package com.xtinno.service.impl;

import cn.com.apollo.platformbase.util.IdUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.xtinno.pojo.DocumentParagraphDTO;
import com.xtinno.pojo.ModelRequest;
import com.xtinno.pojo.ResponseData;
import com.xtinno.pojo.dto.ElementReasoningDTO;
import com.xtinno.pojo.dto.ElementReasoningResDTO;
import com.xtinno.pojo.dto.ElementReasoningRuleDTO;
import com.xtinno.pojo.llm.VLLMResponseDTO;
import com.xtinno.pojo.po.PromptTemplatePO;
import com.xtinno.pojo.po.RuleConditionPO;
import com.xtinno.pojo.po.RuleContentPO;
import com.xtinno.pojo.po.RuleInfoPO;
import com.xtinno.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringSubstitutor;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 要素推导服务
 *
 * @author GuanLei
 * @time 2025/5/6 16:48
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ElementReasoningServiceImpl implements ElementReasoningService {

    private final RuleInfoService ruleInfoService;

    private final RuleConditionService ruleConditionService;

    private final RuleContentService ruleContentService;

    private final PromptTemplateService promptTemplateService;

    private final DataAnalysisService dataAnalysisService;

    private final ModelService modelService;


    @Value("${model.name:Qwen/Qwen2.5-7B-Instruct}")
    private String model;

    @Value("${model.stream:false}")
    private Boolean stream;

    @Value("${model.temperature:0.2}")
    private Double temperature;

    @Value("${model.seed:42}")
    private Integer seed;

    @Value("${model.top_p:0.9}")
    private float top_p;

    @Override
    public Object elementReasoning(ElementReasoningDTO reasoningDTO){
        if (reasoningDTO.getSetLog()){
            ElementReasoningResDTO reasoningResDTO = new ElementReasoningResDTO();
            List<String> logList = new ArrayList<>();
            JSONArray reasoningResult = elementReasoning(reasoningDTO, logList);
            reasoningResDTO.setReasoningResult(reasoningResult);
            reasoningResDTO.setLogList(logList);
            return reasoningResDTO;
        }
        return elementReasoning(reasoningDTO, null);
    }


    private JSONArray elementReasoning(ElementReasoningDTO elementReasoningDto,List<String> logList) {
        JSONArray resultArray = new JSONArray();
        String caseElementCenter = getCaseElementCenter(elementReasoningDto);
        log.info("要素推理入参：【{}】",  JSONObject.toJSONString(elementReasoningDto));
        setLog(logList,"要素推理开始=====================>>> 入参："+ JSONObject.toJSONString(elementReasoningDto));
        List<RuleConditionPO> ruleConditionPOs = getRuleConditionPOS(elementReasoningDto);
        setLog(logList,"获取到的规则列表："+ JSONObject.toJSONString(ruleConditionPOs));
        if  (CollectionUtils.isEmpty(ruleConditionPOs)){
            return resultArray;
        }
        List<CompletableFuture<Object>> futures = new ArrayList<>();
        try(ExecutorService executor = Executors.newFixedThreadPool(ruleConditionPOs.size())){
            for (RuleConditionPO ruleConditionPO : ruleConditionPOs){
                if ("b1".equals(elementReasoningDto.getCaseType()) && "jb1".equals(ruleConditionPO.getCaseType())){
                    continue; //处理bug：规则检索时，案件类型检索条件为“包含”，导致b1的案件检索到jb1的规则。
                }
                String content;
                if (StringUtils.isNotEmpty(elementReasoningDto.getParagraph())){
                    content = elementReasoningDto.getParagraph(); //传入了段落内容，就直接用段落内容
                }else {
                    content = buildContent(elementReasoningDto.getCaseId(),ruleConditionPO.getRuleId(),caseElementCenter);
                }
                if (StringUtils.isEmpty(content)){
                    continue;
                }
                CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
                    RuleInfoPO ruleInfoPO = ruleInfoService.getRuleInfoPoByRuleId(ruleConditionPO.getRuleId());
                    String prompt = buildPrompt(ruleInfoPO, content);
                    return requestModel(prompt,ruleInfoPO.getJsonSchema(),logList);
                }, executor);
                futures.add(future);
            }
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();//阻塞等待所有任务都完成并收集结果
            for (CompletableFuture<Object> future : futures) {
                Object result = future.getNow(null);
                if (Objects.nonNull(result)) {
                    resultArray.add(result);
                }
            }
        }
        setLog(logList,"<<<<<<===================本次要素推理完成===================>>>>>> ");
        return resultArray;
    }

    private static String getCaseElementCenter(ElementReasoningDTO elementReasoningDto) {
        if (StringUtils.isNotEmpty(elementReasoningDto.getCaseElementCenter())){
            byte[] decodedBytes = Base64.getDecoder().decode(elementReasoningDto.getCaseElementCenter());
            elementReasoningDto.setCaseElementCenter(null);//只是为了日志里输出太多xml内容
            return new String(decodedBytes, StandardCharsets.UTF_8);
        }
        return StringUtils.EMPTY;
    }

    /**
     * 通过推理阶段、案件类型、案由、文书类型，查询指定的推理规则列表
     */
    private List<RuleConditionPO> getRuleConditionPOS(ElementReasoningDTO elementReasoningDto) {
        RuleConditionPO ruleConditionPO = new RuleConditionPO();
        ruleConditionPO.setReasoningStage(elementReasoningDto.getReasoningStage());
        ruleConditionPO.setCaseType(elementReasoningDto.getCaseType());
        ruleConditionPO.setCause(elementReasoningDto.getCause());
        ruleConditionPO.setDocumentType(elementReasoningDto.getDocumentType());
        return ruleConditionService.getRuleConditionPOs(ruleConditionPO);
    }

    /**
     * 构建询问大模型的原文内容（根据配置的来源类型获取具体内容）
     */
    private String buildContent(String caseId, String ruleId, String caseXml) {
        StringBuilder content = new StringBuilder();
        List<RuleContentPO> ruleContentPOS = ruleContentService.getRuleContentPOsByRuleId(ruleId);
        for (RuleContentPO ruleContentPO : ruleContentPOS){
            String sourceConfig = ruleContentPO.getSourceConfig();
            if ("1".equals(ruleContentPO.getSourceType())){ //来源文书段落
                String[] split = sourceConfig.split(":");
                String documentType = split[0];
                String[] paragraphCodes = split[1].toLowerCase().replace("\n","").split(",");
                ResponseData<DocumentParagraphDTO> responseData;
                try {
                    responseData = dataAnalysisService.getDocumentParagraph(caseId, documentType);
                } catch (Exception e) {
                    log.warn("调用底层获取段落内容失败！案件ID：【{}】，文书类型：【{}】，失败原因：【{}】",caseId,documentType,e.getMessage());
                    continue;
                }
                if (Objects.nonNull(responseData) && Objects.nonNull(responseData.getBody())){
                    List<DocumentParagraphDTO.AnDuanluo> anDuanluos = responseData.getBody().getAn_Duanluos();
                    for (DocumentParagraphDTO.AnDuanluo anDuanluo : anDuanluos){
                        if (ArrayUtils.contains(paragraphCodes, anDuanluo.getDuanluobianhao().toLowerCase())){
                            content.append(anDuanluo.getDuanluoneirong()).append("\n");
                        }
                    }
                }
            }
            if ("2".equals(ruleContentPO.getSourceType())){ //来源案件要素中心
                String xmlResult = "";
                if(sourceConfig.startsWith("{")){
                    xmlResult = parseCaseXml(caseXml, JSONObject.parseObject(sourceConfig));
                }else {
                    xmlResult = parseCaseXml(caseXml, sourceConfig);
                }
                if (StringUtils.isNotEmpty(xmlResult)){
                    content.append(xmlResult).append("\n");
                }
            }
        }
        log.info("案件ID【{}】执行规则ID【{}】构建出的内容为：【{}】", caseId,ruleId,content);
        return content.toString();
    }

    /**
     * 解析案件要素中心xml，获取对应的xpath内容
     * xpath是含有父子对应关系的写法
     */
    private String parseCaseXml(String caseXml, JSONObject xpathObject){
        if (StringUtils.isEmpty(caseXml)){
            return StringUtils.EMPTY;
        }
        JSONArray resultArray = new JSONArray();
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(new ByteArrayInputStream(caseXml.getBytes(StandardCharsets.UTF_8)));
            buildContent(document, xpathObject, resultArray);
        } catch (Exception e) {
            log.error("解析案件要素中心xml，构建内容异常.........案件要素中心xml：【{}】，解析方式配置：【{}】", caseXml,xpathObject,e);
        }
        if (resultArray.isEmpty()){
            return StringUtils.EMPTY;
        }
        return JSONObject.toJSONString(resultArray);
    }

    private void buildContent(Document document, JSONObject xpathObject, JSONArray resultArray) {
        String parentXPath = xpathObject.getString("parentXPath");
        List<Node> nodeList = document.selectNodes(parentXPath);
        for (Node node : nodeList) {
            if (node instanceof Element detail) {
                JSONObject levelOut = new JSONObject();
                for (String key : xpathObject.keySet()) {
                    if ("parentXPath".equals(key) || "child".equals(key)) {
                        continue;
                    }
                    String path = xpathObject.getString(key);
                    Node valueNode = detail.selectSingleNode(path);
                    if (valueNode != null) {
                        levelOut.put(key, valueNode.getText());
                    } else {
                        levelOut.put(key, "-");
                    }
                }
                if (xpathObject.containsKey("child")) {
                    JSONArray childConfigs = xpathObject.getJSONArray("child");
                    for (int i = 0; i < childConfigs.size(); i++) {
                        JSONObject childConfig = childConfigs.getJSONObject(i);
                        JSONArray childResults = new JSONArray();
                        buildContent(document, childConfig, childResults);
                        levelOut.put("child", childResults);
                    }
                }
                resultArray.add(levelOut);
            }
        }
    }

    /**
     * 解析案件要素中心xml，获取对应的xpath内容
     */
    private String parseCaseXml(String caseXml, String xpath){
        if (StringUtils.isEmpty(caseXml)){
            return StringUtils.EMPTY;
        }
        StringBuilder result = new StringBuilder();
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(new ByteArrayInputStream(caseXml.getBytes(StandardCharsets.UTF_8)));
            List<Node> nodes = document.selectNodes(xpath);
            if (CollectionUtils.isNotEmpty(nodes)){
                Node node = nodes.getFirst();
                if (node instanceof Attribute){
                    for (Object node1 : nodes){
                        Attribute attribute = (Attribute) node1;
                        result.append(attribute.getName()).append(":").append(attribute.getText()).append("\n");
                    }
                }
                if (node instanceof Element){
                    for (Object node1 : nodes) {
                        Element element = (Element) node1;
                        result.append(element.asXML()).append("\n");
                    }
                }
            }
        } catch (Exception e) {
            log.error("解析案件要素中心xml，构建内容异常.........案件要素中心xml：【{}】，解析XPath配置：【{}】", caseXml,xpath,e);
        }
        return result.toString();
    }

    /*
    字典转换，将xpath获取到的内容是字典类型的转换成具体的字典值
    private String dictConvert(String ruleId, String content){
        RuleInfoPO ruleInfoPO = ruleInfoService.getRuleInfoPoByRuleId(ruleId);
        if (StringUtils.isEmpty(ruleInfoPO.getDictConfig())){
            return content;//不需要转字典的直接返回
        }
        JSONArray contentArray = new JSONArray();
        if (content.startsWith("[") || content.startsWith("{")){
            contentArray = JSONArray.parseArray(content);
        }
        JSONObject dictConfig = JSONObject.parseObject(ruleInfoPO.getDictConfig());
        for (String key : dictConfig.keySet()) {
            if (!content.contains(key)){
                continue;
            }
            String dictCode = dictConfig.getString(key);
            SystemRespDTO<DictDTO> dictResult = systemAPIService.getDictByDictCode(dictCode);
            if (dictResult == null || CollectionUtils.isEmpty(dictResult.getBody())){
                log.error("字典转换异常，字典编号【{}】不存在........",dictCode);
                continue;
            }
            List<DictDTO> dictDTOS = dictResult.getBody();
            convertDictName(key, contentArray, dictDTOS);
        }
        return contentArray.toString();
    }

    private void convertDictName(String key, JSONArray contentArray, List<DictDTO> dictDTOS) {
        for (Object content : contentArray){
            JSONObject contentObject = (JSONObject) content;
            String value = contentObject.getString(key);
            if (StringUtils.isNotEmpty(value)){
                DictDTO dictDTO1 = dictDTOS.stream().filter(dictDTO -> dictDTO.getAM_Code().equals(value)).findFirst().orElse(null);
                if (Objects.nonNull(dictDTO1)){
                    contentObject.put(key,dictDTO1.getAM_Name());
                }
            }
            if (contentObject.containsKey("child")){
                JSONArray childArray = contentObject.getJSONArray("child");
                convertDictName(key, childArray, dictDTOS);
            }
        }
    }*/

    /**
     * 构建大模型提示词
     */
    private String buildPrompt(RuleInfoPO ruleInfoPO, String content){
        PromptTemplatePO promptTemplatePO = promptTemplateService.getPromptTemplatePoByTemplateCode(ruleInfoPO.getPromptTemplate());
        Map<String, String> contentMap = new HashMap<>();
        contentMap.put("内容", content);
        contentMap.put("规则描述", ruleInfoPO.getRuleDescription());
        contentMap.put("输出示例", ruleInfoPO.getOutputExample());
        StringSubstitutor substitute = new StringSubstitutor(contentMap);
        return substitute.replace(promptTemplatePO.getTemplateContent());
    }

    /**
     * 请求大模型
     * @param prompt 提示词
     */
    private Object requestModel(String prompt,String jsonSchema,List<String> logList) {
        ModelRequest modelRequest = new ModelRequest();
        modelRequest.setModel(model);
        modelRequest.setStream(stream);
        modelRequest.setTemperature(temperature);
        modelRequest.setSeed(seed);
        modelRequest.setTop_p(top_p);
        modelRequest.setGuided_json(jsonSchema);
        ModelRequest.Message message = new ModelRequest.Message();
        message.setContent(prompt);
        message.setRole("user");
        modelRequest.setMessages(new ModelRequest.Message[]{message});
        try{
            log.info("请求大模型参数：【{}】",JSONObject.toJSONString(modelRequest));
            setLog(logList,"请求大模型提示词："+ prompt);
            VLLMResponseDTO vllmResponseDTO = modelService.requestModel(modelRequest);
            log.info("请求大模型返回结果：【{}】",JSONObject.toJSONString(vllmResponseDTO));
            if (Objects.nonNull(vllmResponseDTO) && CollectionUtils.isNotEmpty(vllmResponseDTO.getChoices()) &&
                    Objects.nonNull(vllmResponseDTO.getChoices().getFirst()) && Objects.nonNull(vllmResponseDTO.getChoices().getFirst().getMessage()) &&
                    StringUtils.isNotEmpty(vllmResponseDTO.getChoices().getFirst().getMessage().getContent())){
                String content = vllmResponseDTO.getChoices().getFirst().getMessage().getContent();
                setLog(logList,"请求大模型返回结果："+content);
                String[] split = content.split("</think>");
                if (ArrayUtils.isNotEmpty(split) && split.length>1){
                    String result = split[1].replace("```", "").replace("json", "").replace("\n","").replace("\\","");
                    if (StringUtils.startsWith(result,"{")){
                        return JSONObject.parseObject(result, JSONObject.class);
                    }else {
                        return JSONObject.parseObject(result, JSONArray.class);
                    }
                }
            }
        } catch (Exception e) {
            log.error("请求大模型异常..............",e);
            setLog(logList,"请求大模型异常!："+e.getMessage());
        }
        return null;
    }

    @Override
    public Boolean elementReasoningRuleImport(MultipartFile ruleFile) {
        List<ElementReasoningRuleDTO> elementReasoningRuleDTOList = List.of();
        try {
            elementReasoningRuleDTOList = readExcelData(ruleFile.getInputStream(), ElementReasoningRuleDTO.class);
        } catch (IOException e) {
            log.error("读取要素推理规则Excel失败........",e);
        }
        List<RuleInfoPO> ruleInfoPOS = new ArrayList<>();
        List<RuleConditionPO> ruleConditionPOS = new ArrayList<>();
        List<RuleContentPO> ruleContentPOS = new ArrayList<>();
        for (ElementReasoningRuleDTO elementReasoningRuleDTO : elementReasoningRuleDTOList) {
            String ruleId = IdUtil.fastUUID();
            RuleInfoPO ruleInfoPO = new RuleInfoPO();
            ruleInfoPO.setId(ruleId);
            ruleInfoPO.setRuleCode(elementReasoningRuleDTO.getRuleCode());
            ruleInfoPO.setRuleDescription(elementReasoningRuleDTO.getRuleDescription());
            ruleInfoPO.setPromptTemplate(elementReasoningRuleDTO.getPromptTemplate());
            ruleInfoPO.setOutputExample(elementReasoningRuleDTO.getOutputExample());
            ruleInfoPOS.add(ruleInfoPO);

            List<RuleContentPO> ruleContentPOList = new ArrayList<>();
            String sourceConfig = elementReasoningRuleDTO.getSourceConfig();
            String[] sourceConfigList = sourceConfig.split("&&&");
            StringBuilder documentType = new StringBuilder();
            for (String sourceConfig1 : sourceConfigList){
                if ("1".equals(elementReasoningRuleDTO.getSourceType())){ //内容来源类型为文书的
                    String[] split = sourceConfig1.split(":");
                    documentType.append(split[0]).append(",");//多个文书类型以英文逗号分割
                }
                RuleContentPO ruleContentPO = new RuleContentPO();//按照不同的配置，分成多条记录存储
                ruleContentPO.setRuleId(ruleId);
                ruleContentPO.setSourceType(elementReasoningRuleDTO.getSourceType());
                ruleContentPO.setSourceConfig(sourceConfig1.replace("\n",""));
                ruleContentPOList.add(ruleContentPO);
            }
            ruleContentPOS.addAll(ruleContentPOList);

            RuleConditionPO ruleConditionPO = new RuleConditionPO();
            ruleConditionPO.setRuleId(ruleId);
            ruleConditionPO.setReasoningStage(elementReasoningRuleDTO.getReasoningStage());
            ruleConditionPO.setCaseType(elementReasoningRuleDTO.getCaseType()); //多个案件类型以英文逗号分割
            ruleConditionPO.setCause(elementReasoningRuleDTO.getCause()); //多个案由以英文逗号分割
            ruleConditionPO.setDocumentType(documentType.toString());
            ruleConditionPOS.add(ruleConditionPO);
        }
        ruleInfoService.saveRuleInfos(ruleInfoPOS);
        ruleContentService.saveRuleContents(ruleContentPOS);
        ruleConditionService.saveRuleConditions(ruleConditionPOS);
        return true;
    }

    /**
     * 一次性读取Excel全部内容
     */
    private <T> List<T> readExcelData(InputStream is, Class<T> clazz) {
        return EasyExcel.read(is).head(clazz).sheet(0).headRowNumber(1).doReadSync();
    }

    /**
     * 记录日志
     */
    private void setLog(List<String> logList, String log) {
        if (Objects.nonNull(logList)) {
            synchronized (this) {
                logList.add(log);
            }
        }
    }

}
